/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/* Generator: Checksum generation methods.
$Id: Generator.java,v 1.12 2003/07/20 04:26:13 rsdio Exp $

Copyright (C) 2003  Casey Marshall <rsdio@metastatic.org>
Copyright (C) 2011  Tomas Hlavnicka <hlavntom@fel.cvut.cz>
 * 
This file is a part of Jarsync.

Jarsync is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at
your option) any later version.

Jarsync is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with Jarsync; if not, write to the

Free Software Foundation, Inc.,
59 Temple Place, Suite 330,
Boston, MA  02111-1307
USA

Linking Jarsync statically or dynamically with other modules is
making a combined work based on Jarsync.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination. */
package io.milton.zsync;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Checksum generation methods.
 *
 * @version $Revision: 1.12 $
 */
public class Generator {

	public Generator() {
	}





	/**
	 * Generate checksums over an entire byte array, with a specified
	 * base offset. This <code>baseOffset</code> is added to the offset
	 * stored in each {@link ChecksumPair}.
	 *
	 * @param buf        The byte array to checksum.
	 * @param baseOffset The offset from whence this byte array came.
	 * @return A {@link java.util.List} of {@link ChecksumPair}s
	 *    generated from the array.
	 * @see #generateSums(byte[],int,int,long)
	 */
	public List generateSums(byte[] buf, long baseOffset, Configuration config) {
		return generateSums(buf, 0, buf.length, baseOffset, config);
	}

	/**
	 * Generate checksums over a portion of abyte array, with a specified
	 * base offset. This <code>baseOffset</code> is added to the offset
	 * stored in each {@link ChecksumPair}.
	 *
	 * @param buf        The byte array to checksum.
	 * @param off        From whence in <code>buf</code> to start.
	 * @param len        The number of bytes to check in
	 *                   <code>buf</code>.
	 * @param baseOffset The offset from whence this byte array came.
	 * @return A {@link java.util.List} of {@link ChecksumPair}s
	 *    generated from the array.
	 */
	private List generateSums(byte[] buf, int off, int len, long baseOffset, Configuration config) {
		int count = (len + (config.blockLength - 1)) / config.blockLength;
		int remainder = len % config.blockLength;
		int offset = off;
		List sums = new ArrayList(count);

		for (int i = 0; i < count; i++) {
			int n = Math.min(len, config.blockLength);
			ChecksumPair pair = generateSum(buf, offset, n, offset + baseOffset, config);
			pair.seq = i;

			sums.add(pair);
			len -= n;
			offset += n;
		}

		return sums;
	}

	/**
	 * Generate checksums for an entire file.
	 *
	 * @param f The {@link java.io.File} to checksum.
	 * @return A {@link java.util.List} of {@link ChecksumPair}s
	 *    generated from the file.
	 * @throws java.io.IOException if <code>f</code> cannot be read from.
	 */
	public List generateSums(File f, Configuration config) throws IOException {
		long len = f.length();
		int count = (int) ((len + (config.blockLength + 1)) / config.blockLength);
		long offset = 0;
		FileInputStream fin = new FileInputStream(f);
		List sums = new ArrayList(count);
		int n = config.blockLength;
		byte[] buf = new byte[n];

		for (int i = 0; i < count; i++) {
			int l = fin.read(buf, 0, n);
			if (l == -1) {
				break;
			}
			/*
			 * V pripade, ze mnozstvi dat nevyplni celou blocksize, vyplnime
			 * nezaplnene misto v bufferu nulami
			 */
			if (n < config.blockLength) {
				Arrays.fill(buf, n, config.blockLength, (byte) 0);
			}

			/* spocita sumy pouze pokud je mnozstvi dat vetsi nez 0,
			 * a spocita je pro cely blok velikosti blocksize
			 * data, ktera nevyplnila celou blocksize jsou doplnena nulami
			 * do velikosti blocksize.
			 */
			if (l > 0) {
				ChecksumPair pair = generateSum(buf, 0, config.blockLength /* not in zsync -> Math.min(l, n)*/, offset, config);
				pair.seq = i;

				sums.add(pair);
				len -= n;
				offset += n;
				n = (int) Math.min(len, config.blockLength);
			}
		}
		fin.close();
		return sums;
	}

	/**
	 * Generate checksums for an InputStream.
	 *
	 * @param in The {@link java.io.InputStream} to checksum.
	 * @return A {@link java.util.List} of {@link ChecksumPair}s
	 *    generated from the bytes read.
	 * @throws java.io.IOException if reading fails.
	 */
	public List generateSums(InputStream in, Configuration config, MessageDigest sha1) throws IOException, NoSuchAlgorithmException {
		List sums = null;
		byte[] buf = new byte[config.blockLength * config.blockLength];
		long offset = 0;
		int len = 0;

		while ((len = in.read(buf)) != -1) {
			sha1.update(buf, 0, len);
			if (sums == null) {
				sums = generateSums(buf, 0, len, offset, config);
			} else {
				sums.addAll(generateSums(buf, 0, len, offset, config));
			}
			offset += len;
		}

		return sums;
	}

	/**
	 * Generate a sum pair for an entire byte array.
	 *
	 * @param buf The byte array to checksum.
	 * @param fileOffset The offset in the original file from whence
	 *    this block came.
	 * @return A {@link ChecksumPair} for this byte array.
	 */
	public ChecksumPair generateSum(byte[] buf, long fileOffset, Configuration config) {
		return generateSum(buf, 0, buf.length, fileOffset, config);
	}

	/**
	 * Generate a sum pair for a portion of a byte array.
	 *
	 * @param buf The byte array to checksum.
	 * @param off Where in <code>buf</code> to start.
	 * @param len How many bytes to checksum.
	 * @param fileOffset The original offset of this byte array.
	 * @return A {@link ChecksumPair} for this byte array.
	 */
	public ChecksumPair generateSum(byte[] buf, int off, int len, long fileOffset, Configuration config) {
		ChecksumPair p = new ChecksumPair();
		config.weakSum.check(buf, off, len);
		config.strongSum.update(buf, off, len);
		if (config.checksumSeed != null) {
			config.strongSum.update(config.checksumSeed, 0,
					config.checksumSeed.length);
		}
		p.weak = config.weakSum.getValue();
		p.strong = new byte[config.strongSumLength];
		System.arraycopy(config.strongSum.digest(), 0, p.strong, 0,
				p.strong.length);
		p.offset = fileOffset;
		p.length = len;
		return p;
	}

	public int generateWeakSum(byte[] buf, int offset, Configuration config) {
		config.weakSum.first(buf, offset, config.blockLength);
		return config.weakSum.getValue();
	}

	public int generateRollSum(byte b, Configuration config) {
		config.weakSum.roll(b);
		return config.weakSum.getValue();
	}

	public byte[] generateStrongSum(byte[] buf, int off, int len, Configuration config) {
		config.strongSum.update(buf, off, len);
		byte[] strongSum = new byte[config.strongSumLength];
		System.arraycopy(config.strongSum.digest(), 0, strongSum, 0, strongSum.length);
		return strongSum;
	}
}
